﻿using System;
using System.Collections.Generic;
using System.Xml.Serialization;
using md.imi.membranes.objects.membranes;
using System.Threading;
using System.Threading.Tasks;
using GASS.CUDA.BLAS;

namespace md.imi.membranes.objects.psystems
{
    public class PSystemsTree : PSystem, IDisposable
    {

        private PSystemsTree parrentConfig;
        #region Synchronization 
        private bool isAllowedToRun;
        public bool IsAllowedToRun
        {
            get
            {
                lock (this)
                {
                    return isAllowedToRun;
                }
            }

            set
            {
                lock (this)
                {
                    isAllowedToRun = value;
                }
                
            }
        }
        #endregion


        public PSystemsTree()
        {
            // TODO: Complete member initialization
            this.parrentConfig = null; //  new PSystemsTree();
            this.PSystems = null;
            PathsCalculated = false;
        }

        public PSystemsTree(PSystemsTree parrentConfig)
            : base(parrentConfig)
        {
            // TODO: Complete member initialization
            this.parrentConfig = parrentConfig;
            this.EditorForm = parrentConfig.EditorForm;
            this.Label = parrentConfig.Label;
            this.PathsCalculated = parrentConfig.PathsCalculated;
        }

        /// <summary>
        ///     The associated editor form
        /// </summary>
        [XmlIgnore]
        public object EditorForm { get; set; }

        /// <summary>
        ///     Holds the name of the curently loaded project.
        /// The value can be automatically generated by the system when a step is performed,
        /// or it contains a name which is defined by the user (using rename configuration option)
        /// </summary>
        public String ProjectName { get; set; }

        /// <summary>
        ///
        /// </summary>
        [XmlArray("PSystemsTree"), XmlArrayItem("PSystemsTree", typeof (PSystemsTree))]
        public List<PSystemsTree> PSystems { get; set; }

        /// <summary>
        ///     Iterate trough list of child membranes, and dispose them recursively
        /// </summary>
        /// <param name="membranes"></param>
        public void Dispose(List<PSystemsTree> psystems)
        {
            if (psystems == null) return;
            foreach (var item in psystems)
            {
                item.Dispose();
            }
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public new void Dispose()
        {
            base.Dispose();
            this.Dispose(PSystems);
        }

        /// <summary>
        ///     The configuration has been changed programatically
        /// (perform GUI update if it is required).
        /// </summary>
        internal void OnChange()
        {
            // throw new NotImplementedException();
        }

        /// <summary>
        ///     Method searches trough memranes of this configuration, 
        /// by holding one upper level membrane, and once the membrane
        /// is identified (by it's ID), the saved one level upper membrane
        /// is returned ad parent membrane
        /// </summary>
        /// <param name="membrane">The membrane to search in configuration</param>
        /// <returns>The parent membrane of the passed parameter. Null if 
        ///  the membrais not found, or it is an Environment membrane</returns>
        public MembranesTree GetMembraneParent(MembranesTree membrane)
        {
            // check parameters
            if (membrane == null || Membranes == null)
            {
                Console.WriteLine("Null input data");
                return null;
            }

            // validate membrane (check Environment membrane)
            if (membrane.Label == "Environment")
            {
                Console.WriteLine("Cannod determine parrent of Envoronment membrane");
                return null;
            }

            return GetMembraneParent(membrane, this.Membranes);
        }


        /// <summary>
        ///     
        /// </summary>
        /// <param name="membrane"></param>
        /// <returns></returns>
        internal MembranesTree GetMembraneParent(MembranesTree membrane, MembranesTree membranes)
        {
            if (membrane == null || membranes == null)
            {
                return null;
            }

            foreach (var m  in membranes.Membranes)
            {
                if (m.Id == membrane.Id)
                {
                    return membranes;
                }

                // search deeper    
                var parentMembr = GetMembraneParent(membrane, m);
                if (parentMembr != null)
                {
                    return parentMembr;
                }
            }

            return null;
        }

        /// <summary>
        ///     Interate trough all PSystems and calculate paths of leaf configurations
        ///  - if a PSystem has null or zero PSystems list
        /// </summary>
        /// <returns></returns>
        public bool CalculateLeafPaths()
        {
            IsAllowedToRun = true;
            return CalculateLeafPaths(this, false);
        }

        public bool ParallelCalculateLeafPaths()
        {
            IsAllowedToRun = true;
            return CalculateLeafPaths(this, true);
        }


        public bool CUDACalculateLeafPaths()
        {
            IsAllowedToRun = true;
            return CalculateLeafPaths(this, true);
        }



        public bool CalculateLeafPaths(PSystemsTree ps, bool doInparallel)
        {
            bool result = false;

            if (ps == null || IsAllowedToRun == false)
            {
                return false;
            }

            if (ps.PSystems == null || ps.PSystems.Count == 0)
            {
                if (ps.CalculateNextPath())
                {
                    result |= ps.ApplyAllSolutions(ps);
                }

                ps.PathsCalculated = true;
                return result;
            }


            if (doInparallel)
            {
                Parallel.For(0, ps.PSystems.Count, i =>
                {
                    result |= CalculateLeafPaths(ps.PSystems[i], true);
                });
            } else
            {
                foreach (var pSystem in ps.PSystems)
                {
                    result |= CalculateLeafPaths(pSystem, false);
                }
                
            }


            return result;
        }

        /// <summary>
        ///     The indicator used to determine if paths were calculated and there are no 
        /// more solutions, or the path was not caluculated yet, and the next slutions set
        /// is unknown
        /// </summary>
        public bool PathsCalculated { get; set; }

        public string BaseLabel { get; set; }

        public string ProjectFileName { get; set; }

        /// <summary>
        ///     User assigned label for current configuration
        /// </summary>
        public String Label { get; set; }

        public PSystemsTree ParrentConfig
        {
            get { return parrentConfig; }
        }

    }
}